package jxtend.processor.eclipse.visitor.handler.impl;

import jxtend.processor.eclipse.utils.EclipseProcessorUtils;
import jxtend.processor.eclipse.visitor.handler.ASTNodeHandlerVisitorContext;
import jxtend.processor.eclipse.visitor.handler.AbstractASTNodeVisitorHandler;
import jxtend.processor.eclipse.visitor.handler.ProblemSeverity;

import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.ArrayReference;
import org.eclipse.jdt.internal.compiler.ast.Assignment;
import org.eclipse.jdt.internal.compiler.ast.Expression;
import org.eclipse.jdt.internal.compiler.ast.MessageSend;
import org.eclipse.jdt.internal.compiler.lookup.ParameterizedTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.Scope;
import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;

@SuppressWarnings("restriction")
public class ArrayReferenceOnListOrMapHandler extends AbstractASTNodeVisitorHandler {

	@Override
	public boolean shouldResolve(ASTNodeHandlerVisitorContext context, ASTNode node, Scope scope) {
		ASTNode parentNode = context.getParentNode();
		if (parentNode instanceof ArrayReference && node == ((ArrayReference) parentNode).receiver) {
			return true;
		}

		return super.shouldResolve(context, node, scope);
	}

	@Override public boolean endVisitBeforeResolve(ASTNodeHandlerVisitorContext context, ASTNode node, Scope scope) {
		ASTNode parentNode = context.getParentNode();

		boolean nodeIdArrayReferenceWithValidReceiverType = (node instanceof ArrayReference)
				&& ((ArrayReference) node).receiver != null
				&& ((ArrayReference) node).receiver.resolvedType != null
				&& EclipseProcessorUtils.isValidTypeReference(((ArrayReference) node).receiver.resolvedType);
		boolean parentNodeIsAssigningThisNode =
			parentNode instanceof Assignment && ((Assignment) parentNode).lhs == node;

		if ((!nodeIdArrayReferenceWithValidReceiverType || parentNodeIsAssigningThisNode)
				&& (!(node instanceof Assignment) || !(((Assignment) node).lhs instanceof ArrayReference)
						|| ((ArrayReference) ((Assignment) node).lhs).receiver == null
						|| ((ArrayReference) ((Assignment) node).lhs).receiver.resolvedType == null
						|| !EclipseProcessorUtils.isValidTypeReference(
								((ArrayReference) ((Assignment) node).lhs).receiver.resolvedType))) {
			return nodeIdArrayReferenceWithValidReceiverType && parentNodeIsAssigningThisNode ? false : true;
		}

		ArrayReference arrayReference =
			(node instanceof Assignment) ? (ArrayReference) ((Assignment) node).lhs : (ArrayReference) node;

		TypeBinding javaUtilListType = context.getLookupEnvironment().getType(JxtendTypeConstants.JAVA_UTIL_LIST);
		TypeBinding javaUtilMapType = context.getLookupEnvironment().getType(JxtendTypeConstants.JAVA_UTIL_MAP);

		Assignment assignment = (node instanceof Assignment) ? (Assignment) node : null;

		boolean receiverIsJavaUtilList =
			arrayReference.receiver.resolvedType.erasure().isCompatibleWith(javaUtilListType);
		boolean receiverIsJavaUtilMap =
			arrayReference.receiver.resolvedType.erasure().isCompatibleWith(javaUtilMapType);

		if (receiverIsJavaUtilList || receiverIsJavaUtilMap) {
			MessageSend messageSend = new MessageSend();
			messageSend.receiver = arrayReference.receiver;

			if (receiverIsJavaUtilList) {
				if (assignment != null) {
					messageSend.selector = "set".toCharArray();
					messageSend.arguments = new Expression[] { arrayReference.position, assignment.expression };
				} else {
					messageSend.selector = "get".toCharArray();
					messageSend.arguments = new Expression[] { arrayReference.position };
				}
			} else {
				assert receiverIsJavaUtilMap;

				//TODO: check that the 'arrayReference.position' is compatible with the 'K' generics variable of the Map
				if (arrayReference.receiver.resolvedType instanceof ParameterizedTypeBinding) {
					ParameterizedTypeBinding parameterizedReceiverTypeBinding =
						(ParameterizedTypeBinding) arrayReference.receiver.resolvedType;
					if (parameterizedReceiverTypeBinding.arguments != null 
							&& parameterizedReceiverTypeBinding.arguments.length > 1
							&& arrayReference.position != null
							&& arrayReference.position.resolvedType != null
							&& !arrayReference.position.resolvedType.isCompatibleWith(
									parameterizedReceiverTypeBinding.arguments[0])) {
						//TODO: make recording a problem with 'arrayReference.postion'.sourceStart or .sourceEnd works;
						//	now Eclipse with highlight the complete array reference AND ALSO the variable declaration
						//	(in case the 'position' is actually a variable reference)
						context.recordProblemForNode(ProblemSeverity.ERROR, scope.referenceContext(),
								arrayReference.sourceStart, arrayReference.sourceEnd,
								"Type is not compatible with '"
									+ new String(parameterizedReceiverTypeBinding.arguments[0].readableName()) + "': "
									+ new String(arrayReference.position.resolvedType.readableName()));
					}
				}

				if (assignment != null) {
					messageSend.selector = "put".toCharArray();
					messageSend.arguments = new Expression[] { arrayReference.position, assignment.expression };
				} else {
					messageSend.selector = "get".toCharArray();
					messageSend.arguments = new Expression[] { arrayReference.position };
				}
			}
			
			context.replaceNode(messageSend);
		}

		return true;
	}
}
